Nuevamente, vamos a leer primero unos datos...

Lectura de un fichero de datos


In [ ]:
# primero hacemos los imports de turno
import os
import datetime as dt

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display

np.random.seed(19760812)
%matplotlib inline

In [ ]:
# Leemos los datos del fichero 'mast.txt'
ipath = os.path.join('Datos', 'mast.txt')

def dateparse(date, time):
    YY = 2000 + int(date[:2])
    MM = int(date[2:4])
    DD = int(date[4:])
    hh = int(time[:2])
    mm = int(time[2:])
    
    return dt.datetime(YY, MM, DD, hh, mm, 0)
    

cols = ['Date', 'time', 'wspd', 'wspd_max', 'wdir',
        'x1', 'x2', 'x3', 'x4', 'x5', 
        'wspd_std']
wind = pd.read_csv(ipath, sep = "\s*", names = cols, 
                   parse_dates = [[0, 1]], index_col = 0,
                   date_parser = dateparse)

Seleccionando datos

Acceso a los valores como si fuera un numpy array

Podemos acceder a los datos usando indexación como haríamos con un numpy array o como se hace normalmente en Python:

  • En Python la indexación empieza por 0 y el último elemento de la 'rebanada' (slice) no se incluye.

In [ ]:
wind[0:10]

Los índices de un numpy array solo pueden ser enteros.

Acceso a los valores mediante indexación de índices

Además, a diferencia de numpy, podemos acceder por índices que no necesariamente sean enteros:


In [ ]:
wind['2013-09-04 00:00:00':'2013-09-04 01:30:00']

En este segundo ejemplo la indexación se hace mediante strings que son la representación de los índices (etiquetas de las posiciones). Otra cosa a resaltar es que en el slice el último elemento SÍ se incluye.

Selección básica de columna (DataFrame)

En ejemplos anteriores, también hemos visto que podíamos seleccionar columnas usando el nombre de la columna:


In [ ]:
wind['wspd'].head(3)

Depende como sean los nombres de las columnas, se puede acceder a la columna usando la notación 'de punto' (dot notation) pero como no siempre funciona y es propenso a errores os recomiendo no usar esta forma:


In [ ]:
# Esto es equivalente a lo de la celda anterior
wind.wspd.head(3)

In [ ]:
# Un ejemplo de potencial error
df1 = pd.DataFrame(np.random.randn(5,2), columns = [1, 2])
df1

In [ ]:
# Esto dará error
df1.1

In [ ]:
# Para que no dé error
df1[1]

Fancy indexing con Series

También se puede utilizar Fancy indexing con series, como si estuviéramos indexando con una lista o con un array de booleanos:


In [ ]:
# Creamos una serie
wspd = wind['wspd']

# Accedemos a los elementos situados en las posiciones 0, 100 y 1000
print(wspd[[0, 100, 1000]])
print('\n' * 3)

# Usamos los índices en las posiciones 0, 100 y 1000
idx = wspd[[0, 100, 1000]].index
print(idx)
print('\n' * 3)

# Accedemos a los mismos que inicialmente pero usando los índices en lugar de 
# las posiciones de los valores
print(wspd[idx])

Con DataFrames el fancy indexing puede resultar ambiguo y nos dará error de indexación.


In [ ]:
# Intentadlo...

Indexación booleana

Como con numpy, también podemos acceder a los valores mediante condiciones booleanas:


In [ ]:
idx = wind['wspd'] > 35
wind[idx]

Podemos encadenar diferentes condiciones. Por ejemplo, vamos a refinar el resultado anterior:


In [ ]:
idx = (wind['wspd'] > 35) & (wind['wdir'] > 225)
wind[idx]

Usando condiciones booleanas puede resultar menos legible. Desde la versión de pandas 0.13 podemos usar el método query para hacerlo un poco más legible.


In [ ]:
# Para que sea más eficiente hay que tener instalado 'numexpr'
# Que es el engine por defecto. Si no lo tenemos instalado y
# no indicamos el engine ('python') nos dará ImportError
wind.query('wspd > 35 and wdir > 225', engine = 'python')

Mediante estas formas de selección pueden llegar a existir ciertas ambigüedades. Vamos a hacer un pequeño inciso para después volver a formas de selección más avanzadas.

INCISO: Alineamiento de datos al operar con estructuras de datos de Pandas

Cuando realizamos una operación entre dos estructuras de datos de Pandas se produce un efecto de alineamiento muy práctico. Veamos esto mediante ejemplos:


In [ ]:
s1 = pd.Series(np.arange(0,10), index = np.arange(0,10))
s2 = pd.Series(np.arange(10,20), index = np.arange(5,15))
print(s1)
print(s2)

Si ahora realizamos una operación entre ambas Series, donde es posible realizar la operación se realiza y en los índices donde no es posible se representan pero no desaparecen ni nos proporciona un error:


In [ ]:
s1 + s2

Volvemos a la indexación (reordenamos lo visto hasta ahora)

Una de las características básicas de pandas es el etiquetado de filas y de columnas, ello provoca que la indexación sea más compleja que con numpy. Hemos de distinguir entre:

  • selección por etiqueta
  • selección por posición (numpy)

La indexación en Series es más simple puesto que las etiquetas siempre se refieren a los índices ya que la columna es única. Como hemos ido viendo anteriormente de forma un poco difusa, para un DataFrame, la indexación básica selecciona las columnas.

Para elegir una sola columna, como ya hemos visto anteriormente:


In [ ]:
wind['wspd_std']

O, podemos elegir varias columnas:


In [ ]:
wind[['wspd', 'wspd_std']]

Pero mediante una 'rebanada' (slicing) accedemos a los índices:


In [ ]:
wind['2015/01/01 00:00':'2015/01/01 02:00']

Por lo que esto daría error


In [ ]:
wind['wspd':'wdir']

In [ ]:
wind[['wspd':'wdir']]

Vaya lío, ¿no?

Indexación avanzada

Para realizar una indexación más avanzada y menos ambigua disponemos de una serie de métodos:

  • loc: está pensado para basar nuestra indexación en las etiquetas (aunque puede aceptar arrays booleanos).

  • iloc: esta opción se basa en usar las posiciones mediante enteros (como si fuera un numpy array).

  • ix: soporta combinar las dos anteriores.

Estos métodos también están disponibles en las Series pero no serían necesarios ya que la indexación en las Series no debe resultar ambigua.

Veamos como funcionan estos métodos en un DataFrame...

Seleccionamos los primeros tres elementos de las dos primeras columnas ('wspd', 'wspd_max'):


In [ ]:
wind.loc['2013-09-04 00:00:00':'2013-09-04 00:20:00', 'wspd':'wspd_max']

In [ ]:
wind.iloc[0:3, 0:2] # similar a indexación con numpy arrays wind.values[0:3, 0:2]

In [ ]:
wind.ix[0:3, 'wspd':'wspd_max']

Una cuarta forma que no hemos visto hasta ahora de forma deliberada:


In [ ]:
wind[0:3][['wspd', 'wspd_max']]

In [ ]:
wind[['wspd', 'wspd_max']][0:3]

Practiquemos un poco todo esto

  1. Devolved todos los datos de enero de 2014

  2. Calculad la velocidad media de febrero de 2014

  3. Usad el método query para obtener todas las velocidades que provienen del norte (en un rango de $\pm$ 10 º considerando el norte orientado a 0º) y cuya velocidad sea superior a 10 m/s

  4. Lo mismo que el punto anterior pero usando la notación booleana típica de numpy

  5. Todo lo anterior lo podéis hacer usando loc, iloc y/o ix. Practicad con las tres posibilidades.


In [ ]:

Una última curiosidad útil si trabajamos con series temporales

Las estructuras de datos de pandas disponen de un método para seleccionar entre horas:


In [ ]:
wind.between_time('00:00', '00:30').head(20)

In [ ]:
# También funciona en series:
wind['wspd'].between_time('00:00', '00:30').head(20)